#include "stdafx.h"

#include "Routines.h"
#include "Lua-Main.h"
#include "Lua-Msi.h"
#include "ExtractSelf.h"
#include "OSException.h"
#include "Logging.h"
#include "Core.h"

using namespace luabridge;

namespace {

bool HandleLuaFile (lua_State* aState, const String& aFile)
{
    Log((boost::format("processing Lua file %1%") % aFile).str());
    if (luaL_dofile (aState, aFile.c_str ())) {
        throw std::runtime_error((boost::format("Error loading script '%1%': %2%") % aFile % lua_tostring(aState, -1)).str());
    }
    return false;
}

}

ScriptEngine::ScriptEngine()
: mInterpreter(luaL_newstate())
{ }

ScriptEngine::~ScriptEngine()
{
    if (mInterpreter) {
        lua_close(mInterpreter);
        mInterpreter = 0;
    }
}

void ScriptEngine::Call(const String& aFunction, const char* aSignature, ...)
{
    va_list VariableList;
    int Arguments = 0;
    int Results = 0;

    va_start(VariableList, aSignature);
    lua_getglobal(mInterpreter, aFunction.c_str());

    for (size_t Index = 0; Index < strlen(aSignature); ++Index, ++Arguments) {
        switch (aSignature[Index]) {
        case 'b':
            lua_pushboolean(mInterpreter, va_arg(VariableList, bool) ? 1 : 0);
            break;
        case 'd':
            lua_pushnumber(mInterpreter, va_arg(VariableList, double));
            break;
        case 'i':
            lua_pushnumber(mInterpreter, va_arg(VariableList, int));
            break;
        case 's':
            lua_pushstring(mInterpreter, va_arg(VariableList, char*));
            break;
        case '>':
            Index = strlen(aSignature);
            goto DONE_FOR;
        default:
            throw std::runtime_error((boost::format("Invalid option (%1%)") % aSignature[Index]).str());
        }
    }

DONE_FOR:

    size_t Offset = Arguments + 1;

    Results = strlen(aSignature) - Arguments - 1;
    Results = Results < 0 ? 0 : Results;

    if (lua_pcall(mInterpreter, Arguments, Results, 0) != 0) {
        throw std::runtime_error((boost::format("Error running function '%1%': %2%") % aFunction % lua_tostring(mInterpreter, -1)).str());
    }

    int Stack = -Results;

    for (int Index = 1; Index <= Results; ++Stack, ++Index, ++Offset) {
        switch (aSignature[Offset]) {
        case 'b':
            if (lua_isboolean(mInterpreter, Stack) == 0) throw std::runtime_error((boost::format("Result %1% is not a bool, it is a %2%") % Index % lua_typename(mInterpreter, lua_type(mInterpreter, Stack))).str());
            *va_arg(VariableList, bool*) = lua_toboolean(mInterpreter, Stack) != 0;
            break;
        case 'd':
            if (!lua_isnumber(mInterpreter, Stack)) throw std::runtime_error((boost::format("Result %1% is not a number, it is a %2%") % Index % lua_typename(mInterpreter, lua_type(mInterpreter, Stack))).str());
            *va_arg(VariableList, double*) = lua_tonumber(mInterpreter, Stack);
            break;
        case 'i':
            if (!lua_isnumber(mInterpreter, Stack)) throw std::runtime_error((boost::format("Result %1% is not a number, it is a %2%") % Index % lua_typename(mInterpreter, lua_type(mInterpreter, Stack))).str());
            *va_arg(VariableList, int*) = static_cast<int>(lua_tonumber(mInterpreter, Stack));
            break;
        case 's':
            if (!lua_isstring(mInterpreter, Stack)) throw std::runtime_error((boost::format("Result %1% is not a string, it is a %2%") % Index % lua_typename(mInterpreter, lua_type(mInterpreter, Stack))).str());
            *va_arg(VariableList, std::string*) = lua_tostring(mInterpreter, Stack);
            break;
        default:
            throw std::runtime_error((boost::format("Invalid result (%1%)") % aSignature[Offset]).str());
        }
    }

    va_end(VariableList);
    lua_pop(mInterpreter, Results);
}

void ScriptEngine::Initialize(const std::list<String>& aFiles)
{
    Log("Initializing script engine - enter");

    luaL_openlibs(mInterpreter);
    InitializeDefaultSkripts();
    InitializeMsiBinding(mInterpreter);

    getGlobalNamespace(mInterpreter)
        .beginClass<Core> ("Core")
            .addFunction("StartUi", &Core::StartUi)
            .addFunction("Log", &Core::Log)
            .addFunction("SetLabelText", &Core::SetLabelText)
        .endClass()
    ;

    lua_newtable(mInterpreter);
    lua_setglobal(mInterpreter, "InstallerFiles");

    auto Start = aFiles.cbegin();
    auto Stop = aFiles.cend();
    int Counter = 1;

    while (Start != Stop) {
        lua_pushinteger(mInterpreter, Counter);
        lua_pushstring(mInterpreter, Start->c_str());
        lua_settable(mInterpreter, -3);
        ++Counter;
        ++Start;
    }

    lua_pop(mInterpreter, 1);

    Log("Initializing script engine - exit");
}

void ScriptEngine::InitializeDefaultSkripts()
{
    String SearchPattern = PathAppend(WorkingDirectory, TEXT("__*.lua"));
    if (EnumerateFiles(SearchPattern, boost::bind(HandleLuaFile, mInterpreter, _1)) == 0) {
        SearchPattern = PathAppend(ApplicationDirectory, PathAppend("Engine", TEXT("__*.lua")));
        if (EnumerateFiles(SearchPattern, boost::bind(HandleLuaFile, mInterpreter, _1)) == 0) {
            RaiseOsError(TEXT("Unable to find script files"));
        }
    }
}

void ScriptEngine::RegisterCore(Core& aInstaller)
{
    push(mInterpreter, &aInstaller);
    lua_setglobal(mInterpreter, "Core");
}
